﻿/**
 \file scanner.c
 Исходный код лексического сканера.
*/
#include "common.h"
#include "scanner.h"
#include <ctype.h>
#include <string.h>

/** 
 \var FILE *SourceFile
 Дескриптор файла с исходным кодом.
 \var unsigned char CurToken.                      
 Текущая лексема.                            
 \var unsigned char CurSym                        
 Считанный из исходного файла символ.
 \var unsigned long int CurLine                    
 Номер читаемой строки исходного файла.
 \var unsigned long int PosInIdent                 
 Номер позиции в ident.
 \var unsigned char ident[MAX_IDENT]               
 Здесь хранится считанный идентификатор.
 \var unsigned long int LenOfString                 
 Длина строковой лексемы.
 \var unsigned long int PosInString                 
 Текущая позиция в строковой лексеме.
 \var unsigned char *String                         
 Строковая лексема.
 \var unsigned long int integer                     
 Числа, символьные константы.
 \var float floatnum                                
 Дробные константы одинарной точности.
 \var double dblnum                                 
 Дробные константы двойной точности. 
*/ 

FILE *SourceFile = NULL;
unsigned char CurToken = 0;
unsigned char CurSym = 0;
unsigned long int CurLine = 1;
unsigned long int PosInIdent = 0;
unsigned char ident[MAX_IDENT];
unsigned long int LenOfString = 0;
unsigned long int PosInString = 0;
unsigned char *String = NULL;
unsigned long int integer = 0;
float floatnum = 0.0;
double dblnum = 0.0;

/**
 \var char *keywords[NUM_OF_KEYWORDS]
 Ключевые слова.

 Порядковый номер ключевого слова в массиве
 совпадает с номером его лексемы
*/
char *keywords[NUM_OF_KEYWORDS] = 
  {"array","begin","by","case","const",
   "div", "do","end","else","elsif","import",
   "is","if","module","mod","nil","of","or",
   "pointer","procedure","repeat",
   "record","return","to","type","then",
   "true","false","until","union","var","while"};

/**
 \var char operators[]
 Операторы.

 Код лексемы оператора равен сумме его позиции в массиве
 плюс общее число всех ключевых слов NUM_OF_KEYWORDS
*/
char operators[] = "~;+-*/&^|,=#<>[]{}\"\':.()\0";

/**
 \fn unsigned char *GetIdentValue(void)
 Возращает указатель на строку с идентификатором.
*/
unsigned char *GetIdentValue(void)
{
 return ident;
}

/**
 \fn unsigned char ord(unsigned char c)
 Преобразует символ в число.
 \param c Входной символ.
 \return Возвращает числовой код, соответствующий номеру символа в текущей кодировке.
*/
unsigned char ord(unsigned char c)
{
  char temp[17] = "0123456789ABCDEF";
  unsigned char i = 0;

  while (i < 16)
  {
    if (temp[i] == c)
     return i;
    ++i;
  }
  return 255;
}

/**
 \fn void OpenSourceFile(char *filename)
 Открывает исходный файл.
 \param filename Имя исходного файла.
*/
void OpenSourceFile(char *filename)
{
 SourceFile = fopen(filename,"r");
 if (SourceFile == NULL)
 {
  printf("Файл \"%s\" не удалось открыть!\n",filename);
  exit(1);
 }
}

/**
 \fn void CloseSourceFile(void)
 Закрывает исходный файл.
*/
void CloseSourceFile(void)
{
 fclose(SourceFile);
}

/**
 \fn unsigned char GetCurToken(void)
 Возвращает текущую лексему.
*/
unsigned char GetCurToken(void)
{
 return CurToken;
}

/**
 \fn unsigned long int GetCurLine(void)
 Возвращает номер текущей строки в исходном файле.
*/
unsigned long int GetCurLine(void)
{
 return CurLine;
}

/**
 \fn FILE *GetSourceFile(void)
 Возращает дескриптор исходного файла.
*/ 
FILE *GetSourceFile(void)
{
 return SourceFile;
}

/**
 \fn void SkipComment(void)
 Обработка комментариев.
*/
void SkipComment(void)
{
 while (1) 
 {
  CurSym = getc(SourceFile);
  if (feof(SourceFile))
  {
   printf(ETEMPLATE,CurLine,eNotFindEndOfComment); 
   exit(1);
  }
  if (CurSym == '*') {
   CurSym = getc(SourceFile);
   if (CurSym == ')') {
    CurSym = getc(SourceFile);
    break;
   }
  }
 }
}

/**
 \fn void GetIdent(void)
 Читает ключевые слова и идентификаторы из исходного файла.
*/
void GetIdent(void)
{
 while (isalnum(CurSym) && !feof(SourceFile)) {
  ident[PosInIdent] = CurSym;
  ++PosInIdent;
  if (PosInIdent > LENGTH_IDENT)
  {
   printf(ETEMPLATE,CurLine,eTooLongIdent);
   exit(1);
  } 
  CurSym = getc(SourceFile);
 }
 ident[PosInIdent] = 0;
 PosInIdent = 0;
}

/**
 \fn unsigned char GetString(unsigned char mode)
 Читает строковые и символьные лексемы.
 \param mode Символ на котором закончится чтение строки.
             Это, как правило, одинарная или двойная кавычка.
 \return В зависимости от того, что было прочитано, возвращает
         числовой код символьной или строковой лексемы.
*/
unsigned char GetString(unsigned char mode)
{
 fpos_t startpos, endpos;
 unsigned long int c = 0;

 CurSym = getc(SourceFile);
 fgetpos(SourceFile,&startpos);
 while (CurSym != mode && !feof(SourceFile))
  CurSym = getc(SourceFile);
 if (feof(SourceFile))
 {
  printf(ETEMPLATE,CurLine,eNotFindEndOfString);
  exit(1);
 }
 fgetpos(SourceFile,&endpos);
 CurSym = getc(SourceFile);
 --startpos;
 fsetpos(SourceFile,&startpos);
 CurSym = getc(SourceFile);
 LenOfString = endpos - startpos;
 String = (unsigned char *)GetMem(LenOfString,"GetString");
 while (c < (LenOfString-1))
 {
  String[c] = CurSym;
  CurSym = getc(SourceFile);
  ++c;
 }
 String[c] = 0;
 CurSym = getc(SourceFile);
 if (LenOfString == 2)
  return lChar;
 else
  return lString;
}

/**
 \fn void StrToInt(unsigned long int *num, unsigned char *string,
                   unsigned long int startpos, unsigned long int endpos, 
                   unsigned char base, const char *error)

 Преобразует строку цифр в число, проверяя его на
 соответствие диапазону.

 \param num Тут хранится получившееся число.
 \param string Строка с цифрами.
 \param startpos Позиция в string, с которой начинается чтение.
 \param endpos Позиция в string, на которой чтение закачивается.
 \param base Основа системы счисления.
 \param error Текст выводимой ошибки.

 startpos и endpos нужны для возможности преобразования произвольного куска строки
 в число. Это полезно при разборе дробных чисел.
*/
void StrToInt(unsigned long int *num, unsigned char *string,
              unsigned long int startpos, unsigned long int endpos, 
              unsigned char base, const char *error)
{
 unsigned long int i = 0;
 unsigned char c = 0;

 *num = 0;
 i = startpos;
 while (i < endpos)
 {
  if (*num != 0)
  {
   if ((MaxInt / *num) < base)
   {
    printf(ETEMPLATE,CurLine,error);
    exit(1);
   }
   *num = *num * base;
  }
  c = ord(string[i]);
  if (c > base-1)
  {
   printf(ETEMPLATE,CurLine,eHexInDec);
   exit(1);
  }
  if ((MaxInt - *num) < c)
  {
   printf(ETEMPLATE,CurLine,error);
   exit(1);
  }
  *num = *num + c;
  ++i;
 }
}

/**
 \fn void TestFloatNumber(float *flt, double *dbl).
 Проверяет дробное число на переполнение.
 \param flt Проверяемое число, dbl должен быть равен NULL.
 \param dbl Проверяемое число, flt должен быть равен NULL.
*/
void TestFloatNumber(float *flt, double *dbl)
{
 union {
  unsigned long int data;
  float value;
 } f;

 union {
  struct {
   unsigned long int data2;
   unsigned long int data1;
  } data;
  double value;
 } d;

 unsigned long int exp = 0;
 unsigned long int mantissa = 0;

 if (flt != NULL)
  f.value = *flt;
 if (dbl != NULL)
  d.value = *dbl;

 if (flt != NULL)
 {
  exp = (f.data << 1) >> 24;
  mantissa = (f.data << 9) >> 9;
  if ((exp == 0xFF && mantissa == 0) ||
      (exp == 0 && mantissa != 0))
  {
   printf(ETEMPLATE,CurLine,eIntOutOfRange);
   exit(1);
  }
 }
 if (dbl != NULL)
 {
  exp = (d.data.data1 << 1) >> 21;
  mantissa = d.data.data1 << 12;
  if ((exp == 0x7FF && mantissa == 0 && d.data.data2 == 0) ||
      (exp == 0 && mantissa != 0 && d.data.data2 != 0))
  {
   printf(ETEMPLATE,CurLine,eIntOutOfRange);
   exit(1);
  }
 }
}

/**
 \fn void StrToFloat(float *flt, double *dbl, unsigned char *string).
 Преобразует строку в дробное число.
 \param flt Если не равен NULL, то преобразуем строку в дробное число 
            одинарной точности.
 \param dbl Если не равен NULL, то преобразуем строку в дробное число 
            двойной точности.
 \param string Строка с правильно записанным числом.

 flt и dbl взаимоисключающи: если один равен NULL, то второй не может быть равен NULL.
*/
void StrToFloat(float *flt, double *dbl, unsigned char *string)
{
 /* 
  length - длина string.
  pos - текущая позиция в string.
  sign - знак степени. 0 - отрицательный, 1 - положительный
 */

 unsigned long int length = 0;
 unsigned long int pos = 0;
 unsigned long int exp = 0;
 unsigned char sign = 1;
 unsigned char c = 0;

 length = strlen((const char *)string);
 if (flt != NULL)
  *flt = 0.0;
 if (dbl != NULL)
  *dbl = 0.0;

 while (string[pos] != '.')
 {
  c = ord(string[pos]);
  if (c > 9)
  {
   printf(ETEMPLATE,CurLine,eHexInDec);
   exit(1);
  }
  if (flt != NULL)
   *flt = (*flt * 10) + c;
  if (dbl != NULL)
   *dbl = (*dbl * 10) + c;
  ++pos;
 }
 ++pos;
 if (!isdigit(string[pos]))
 {
  printf(ETEMPLATE,CurLine,eNotFindNumberAfterDot);
  exit(1);
 }
 while (pos < length && 
        (string[pos] != 'E' && string[pos] != 'D'))
 {
  c = ord(string[pos]);
  /* Если c - шестнадцатеричная цифра, то ошибка. */
  if (c > 9)
  {
   printf(ETEMPLATE,CurLine,eHexInDec);
   exit(1);
  }
  if (flt != NULL)
   *flt = (*flt * 10) + c;
  if (dbl != NULL)
   *dbl = (*dbl * 10) + c;
  ++pos; ++exp;
 }
 if (flt != NULL)
  while (exp != 0)
  {
   *flt = *flt / 10;
   --exp;
  }
 if (dbl != NULL)
  while (exp != 0)
  {
   *dbl = *dbl / 10;
   --exp;
  }
 
 if ((flt != NULL && string[pos] == 'D') ||
     (dbl != NULL && string[pos] == 'E'))
 {
  printf(ETEMPLATE,CurLine,DOrELetterUsedWrong);
  exit(1);
 }
 if (string[pos] == 'E' || string[pos] == 'D')
 {
  ++pos;
  if (string[pos] == '-')
  {
   ++pos;
   sign = 0;
  }
  if (string[pos] == '+')
   ++pos;
  if (!isdigit(string[pos]))
  {
   printf(ETEMPLATE,CurLine,eNotFindNumberInDegree);
   exit(1);
  }
  StrToInt(&exp,string,pos,strlen((const char *)string),
           10,eIntOutOfRange);
  while (exp != 0)
  {
   if (flt != NULL)
   {
    if (sign == 0)
     *flt = *flt / 10;
    else
     *flt = *flt * 10;
   }
   if (dbl != NULL)
   {
    if (sign == 0)
     *dbl = *dbl / 10;
    else
     *dbl = *dbl * 10;
   }
   --exp;
  }
 }
}

/**
 \fn unsigned char GetNumber(void)
 Читает числовые константы.
*/
unsigned char GetNumber(void)
{
 /* number - строковый буфер. Тут хранится символьный вид числа. *
  * pos - текущая позиция в number.                              *
  * length - длина number.                                       */

 unsigned char *number = NULL;
 unsigned long int pos = 0;
 unsigned long int length = 0;

 /* start - начальная позиция числа в файле, *
  * end - конечная позиция.                  */
 fpos_t start,end;

 /* Считаем число символов, образующих число.*/
 fgetpos(SourceFile,&start);
 while (strchr("+-xXdDeEhH.1234567890",CurSym))
  CurSym = getc(SourceFile);
 fgetpos(SourceFile,&end);

 /* Начальную позицию уменьшаем на 1,               *
  * потому что при чтении она увеличится на единицу *
  * и не будет указывать точно на начало.           */
 --start;

 /* Считаем длину строкового вида числа */
 length = end - start;

 number = (unsigned char *)GetMem(length+1,"GetNumber");
 
 /* Устанавливаем позицию в файле на начало числа и *
  * читаем его в буфер number.                      */
 fsetpos(SourceFile,&start);
 number[length] = 0;
 CurSym = getc(SourceFile);
 while (strchr("+-xXdDeEhH.1234567890",CurSym))
 {
  number[pos] = toupper(CurSym);
  ++pos;
  CurSym = getc(SourceFile);
 }

 /* Если в дробном числе есть буква D, значит это дробное   *
  * число двойной точности, если есть точка - значит        *
  * одинарной точности. Если в конце X - значит это символ. *
  * Если H или цифра - значит целое число.                  */
 if (strstr((const char *)number,"D"))
 {
  StrToFloat(NULL,&dblnum,number);
  TestFloatNumber(NULL,&dblnum);
  free(number);
  return lLongreal;
 }
 if (strstr((const char *)number,"."))
 {
  StrToFloat(&floatnum,NULL,number);
  TestFloatNumber(&floatnum,NULL);
  free(number);
  return lReal;
 }
 /* -2 потому что в конце строки нулевой символ *
  * и массив начинается с нуля.                 */
 if (number[length-2] == 'X')
 {
  StrToInt(&integer,number,0,length-2,16,eCharOutOfRange);
  free(number);
  return lChar;
 }
 if (number[length-2] == 'H')
 {
  StrToInt(&integer,number,0,length-2,16,eIntOutOfRange);
  free(number);
  return lInteger;
 }
 StrToInt(&integer,number,0,length-1,10,eIntOutOfRange);
 free(number);
 return lInteger;
}

/**
 \fn unsigned char TestOnComplexOperator(unsigned char one, 
                                         unsigned char two)
 Проверяет, не образуют ли два считанных символа двухсимвольные операторы.
 \param one Первый символ
 \param two Второй символ
 \return Если найден один из операторов, то возвращается 1, иначе 0.
*/
unsigned char TestOnComplexOperator(unsigned char one, 
                                    unsigned char two)
{
 if (CurSym == one)
 {
  CurSym = getc(SourceFile);
  if (CurSym == two)
  {
   CurSym = getc(SourceFile);
   return 1;
  }
 }
 return 0;
}

/**
 \fn unsigned char Token(void)
 Читает лексему из исходного файла.
 \return Возвращает числовой код лексемы.
*/
unsigned char Token(void)
{
 /* PosInKeywords - номер сравниваемого ключевого слова в массиве keywords *
  * PosInOperators - номер текущей позиции в массива operators             *
  * temp - временная переменная                                            */

 unsigned char PosInKeywords = 0;
 unsigned char PosInOperators = 0;
 unsigned char temp = 0;

 /* Пропускаем пробелы и символы перевода строки */
 while (CurSym == ' ' || CurSym == '\n')
 {
  /* По символу перевода считаем номер текущей строки */
  if (CurSym == '\n')
  {
   CurSym = getc(SourceFile);
   ++CurLine;
  }
  else
   CurSym = getc(SourceFile);
 }
 
 /* Проверка на конец файла */
 if (feof(SourceFile))
  exit(0);

 /* Обрабатываем ключевые слова и идентификаторы */
 if (isalpha(CurSym))
 {
  GetIdent();
  while (PosInKeywords < NUM_OF_KEYWORDS && 
         keywords[PosInKeywords][0] != ident[0])
   ++PosInKeywords;
  while (PosInKeywords < NUM_OF_KEYWORDS &&
         keywords[PosInKeywords][0] == ident[0])
  {
   if(!strcmp(keywords[PosInKeywords],(char *)ident))
    return PosInKeywords;
   ++PosInKeywords;
  }
  return lIdent;
 }
 
 /* Обработка числовых констант */
 if (strchr("1234567890",CurSym))
 {
  temp = GetNumber();
  return temp;
 }

 /* Обработка операторов */
 while (operators[PosInOperators] != 0)
 {
  if (operators[PosInOperators] == CurSym)
  {
   /* Пропуск комментариев */
   if (CurSym == '(')
   {
    CurSym = getc(SourceFile);
    if (CurSym == '*')
    {
     CurSym = getc(SourceFile);
     SkipComment();
     temp = Token();
     return temp;
    }
    return PosInOperators+NUM_OF_KEYWORDS;
   }
   
   if (CurSym == '\"' || CurSym == '\'')
   {
    temp = GetString(CurSym);
    return temp;
   }

   /* Символ присваивания */
   if(TestOnComplexOperator(':','='))
    return lAssign;

   /* Символ диапазона */
   if(TestOnComplexOperator('.','.'))
    return lTwoDot;

   /* Меньше или равно */
   if(TestOnComplexOperator('<','='))
    return lLessEqual;

   /* Больше или равно */
   if(TestOnComplexOperator('>','='))
    return lMoreEqual;

   CurSym = getc(SourceFile);
   return PosInOperators+NUM_OF_KEYWORDS;
  }
  ++PosInOperators;
 }

 /* Если текущий символ не является частью алфавита, *
  * то выдаем ошибку                                 */
 printf(ETEMPLATE,CurLine,eNoAlphaSymbol);
 exit(1);
}

/**
 \fn void GetToken(void)
 Читает следующую лексему.
*/
void GetToken(void)
{
 /* Затравочный символ. Нужно для лексического сканера. *
  * Читается один раз.                                  */

 if (CurSym == 0)
  CurSym = getc(SourceFile);
 CurToken = Token();
}